/**
  ETFRoot project, Anyang Normal University && IMP-CAS
  \class ETFTaTracker
  \brief for tracking around the reaction target. NOTE that this is an abstract
  base class for class ETFPostTaTracker and ETFPreTaTracker.
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2021-11-09
  \date 2021-12-08 last modified
  \attention
  changelog
  <table>
  <tr>  <th>Date         <th>Author      <th>Description                   </tr>
  <tr>  <td>2021-11-09   <td>Asia Sun    <td>file created                  </tr>
  </table>

  \copyright Copyright (c) 2021 Anyang Normal U. && IMP-CAS with LGPLv3 LICENSE
*/

#include <iostream>
#include <TClonesArray.h>
#include "ETFTaTracker.h"
#include "ETFDetectorManager.h"
#include "ETFDC.h"
#include "ETFMath.h"
#include "ETFTrackTa.h"
#include "ETF3DTrack.h"
#include "ETFTimer.h"
#include "ETFDCHit.h"

using std::cout;
using std::endl;

ClassImp(ETFTaTracker);

ETFTaTracker::ETFTaTracker(const char *name) : ETFTracker(name), fTimer(nullptr){
  if(!strcmp(name, "")) return;

  const char xy[] = {'X', 'Y'};
  char tname[128];
  for(int i = 2; i--;){
    fTracks[i] = new TClonesArray("ETFTrackTa");
    sprintf(tname, "ETFTrackTa%c", xy[i]);
    fTracks[i]->SetName(tname);
    fTracksOut[i] = new TClonesArray(*fTracks[i]);
    sprintf(tname, "%s%c", name, xy[i]);
    fTracksOut[i]->SetName(tname);
  } // end if
  f3DTracks = new TClonesArray("ETF3DTrack");
  sprintf(tname, "%s3D", name);
  f3DTracks->SetName(tname);
  fTargetZ = ETFDetectorManager::Instance()->GetTargetZ();

  // TODO: user input configuration expected here
  D2THREPD = 40.;
} // end ctor

ETFTaTracker::~ETFTaTracker(){}

/// provide drift time start
double ETFTaTracker::GetTime() const{
  return fTimer->GetTime();
} // end member function GetTime

int ETFTaTracker::GetTimerFiredStatus() const{
  return fTimer->GetFiredStatus();
} // end member function GetTimerFiredStatus

// X-Y tracking
void ETFTaTracker::Map(){
  if(4 != GetTimerFiredStatus()) return; // not eligible for tracking
  Map(TrackType::kX);
  Map(TrackType::kY);
  TrackMerge();
  UpdateHitPos();
} // end member function Map3D

// 3D tracking
void ETFTaTracker::Map3D(){
  Map();
} // end member functuion Map

// update the hit position of the tracks in the tracker
void ETFTaTracker::UpdateHitPos(){
  ETFTracker::UpdateHitPos(); // update hit position for all the DCs
  const double z = fTargetZ; // just for convenience

  for(auto &c : fTracksOut) if(c) for(const auto &b : *c) if(b){
    ETFVTrack *t = dynamic_cast<ETFVTrack *>(b);
    t->SetHitPos("TA", z*t->GetK()+t->GetB());
  } // end fors and ifs
  if(f3DTracks) for(const auto &b : *f3DTracks) if(b){
    ETF3DTrack *t = dynamic_cast<ETF3DTrack *>(b);
    t->SetHitPos("TA", z*t->GetK1()+t->GetB1(), z*t->GetK2()+t->GetB2());
  } // end for and ifs

  // because postMag DCs do not have a timer, so assignment of timer hitpos are
  // separated from the main UpdateHitPos() method, just to accommodate for DCs around TA
  UpdateHitPos(fTimer);
} // end member function UpdateHitPos

// called by Map, to test if a hit combination is eligible
bool ETFTaTracker::TestCombination(TrackType type){
  const int n = ETFTrackTa::EnoughHit(fHits);
  if(!n) return false;

  vector<double> z, x, dx;
  z.resize(n); x.resize(n); dx.resize(n);
  dx.assign(n, -1.); // dx not unkown, so set to minus
  int i = 0;
  for(auto &p : fHits) if(p.second){
    p.second->GetProjZX(z[i], x[i]); i++;
    if(0) if(fDebug){
      cout << "___________Layer" << p.first << "___________" << endl;
      p.second->Print();
    } // end if(fDebug)
  } // end for over hits
  double k, dk, b, db, d2, q = 0.; // q: goodness of fit
  ETFMath::LinearFit(z.data(), x.data(), dx.data(), n, b, k, db, dk, d2, q);
  if(0) if(fDebug){
    cout << "d2: " << d2 << " D2THREPD: " << D2THREPD << " n: " << n << endl;
    cout << "k: " << k << " b: " << b << endl;
    getchar();
  } // end if(fDebug)
  if(d2 > D2THREPD*n) return false;

  const short t = short(type);
  ETFTrackTa *trk = dynamic_cast<ETFTrackTa *>(ConstructedAt(fTracks[t]));
  // assign the drift time start
  trk->Initialize(); trk->SetDsquare(d2);
  trk->SetId(fTracks[t]->GetEntries()-1, t); // set name&title of the track
  for(auto &p : fHits) if(p.second) trk->AddHit(p.second);
  trk->SetDriftTimeStart(GetTime());
  // here whether drift time is within range is checked
  if(!SetDriftDistance(trk)){ fTracks[t]->Remove(trk); return false; }
  // fit the track with drift distance, and assign the fitting residues
  // using space-time relation specific to post-mag dc, i.e. ETFDC
  // if the fitting result fails the user requirements, returns false
  if(fChiPD > 0.) trk->SetChiPD(fChiPD); // user-input chiPD
  if(!trk->Fit()){ fTracks[t]->Remove(trk); return false; }
  // compare the current track with the existing tracks for compatibility check
  // remove the track if incompatibility is encountered
  if(!Compare(trk, type)){ fTracks[t]->Remove(trk); return false; }
  return true; // new trk passed all the test - We can finally accept them
} // end member function TestCombination

void ETFTaTracker::TrackMerge(){
  if(!f3DTracks) return;
  f3DTracks->Clear();

  if(1 == fTracks[0]->GetEntries() && 1 == fTracks[1]->GetEntries()){ // BINGO
    ETF3DTrack *trk3d = dynamic_cast<ETF3DTrack *>(f3DTracks->ConstructedAt(0));
    ETFVTrack *trkx = dynamic_cast<ETFVTrack *>(GetTrack(0, TrackType::kX));
    ETFVTrack *trky = dynamic_cast<ETFVTrack *>(GetTrack(0, TrackType::kY));
    trk3d->SetTrack(TrackType::kX, trkx);
    trk3d->SetTrack(TrackType::kY, trky);
    trk3d->SetK1B1(trkx->GetK(), trkx->GetB());
    trk3d->SetK2B2(trky->GetK(), trky->GetB());
  } // end if
} // end member function TrackMerge
